package it.tcm.TeacherClassMate.dao;


import it.tcm.TeacherClassMate.conf.ConfigReader;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;


public abstract class GenericDao<T, ID extends Serializable>  {

    private Class<T> persistentClass;
    private Logger log = Logger.getLogger(this.getClass());
    private Session session = null;

    @SuppressWarnings("unchecked")
	public GenericDao() {
	this.persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    
    }

    public void setSession(Session s) {
    	session = s;
    }
    
    protected Session getSession() {
     try {
    		if (session == null) {
    			throw new Exception("Session in null, please supply a valid session object.");
    		}
    	} catch (Exception e) {
    	    log.error("Errore in lettura session ", e);
    	}
    	return session;
      }
       


    /*
    protected Session getSession() {
	Session session = null;
	try {
		session = HibernateUtil.getSession();
	} catch (Exception e) {
	    log.error("Errore in lettura session ", e);
	}
	return session;
    }
    */

    public Class<T> getPersistentClass() {
	return persistentClass;
    }

    @SuppressWarnings("unchecked")
    public T findById(ID id, boolean lock) {
	T entity;
	if (lock)
	    entity = (T) getSession().get(getPersistentClass(), id, LockMode.UPGRADE);
	else
	    entity = (T) getSession().get(getPersistentClass(), id);

	return entity;
    }
    
    @SuppressWarnings("unchecked")
    public T load(ID id) {
    	T entity = (T) getSession().load(getPersistentClass(), id);
    	return entity;
    }

    public List<T> findAll() {
	return findByCriteria(new ArrayList<Order>());
    }

    public void flush() {
	getSession().flush();
    }

    public void clear() {
	getSession().clear();
    }
    
    
    /**
     * Use this inside subclasses as a convenience method.
     */
    @SuppressWarnings("unchecked")
    public T UniqueResultByCriteria(List<Order> orders, Criterion... criterion) {
	Criteria crit = getSession().createCriteria(getPersistentClass());
	for (Criterion c : criterion) {
	    crit.add(c);
	}
	for (Order c : orders) {
	    crit.addOrder(c);
	}
	return (T)crit.uniqueResult();
    }
    
    
    /**
     * Utilizzare questo metodo per gestire criterion su collection/set, fornendo il Criteria(con settati alias per le collection) in input.
     */
    @SuppressWarnings("unchecked")
    public T UniqueResutlByCriteria(Criteria crit, List<Order> orders, Criterion... criterion) {
	//se il criteria fornito in ingresso  null, lo creo
	if (crit == null) {
	    crit = getSession().createCriteria(getPersistentClass());
	}
	crit.setMaxResults(ConfigReader.getInstance().getIntProperty(ConfigReader.MAX_ROW_RESULT));
	for (Criterion c : criterion) {
	    crit.add(c);
	}
	for (Order c : orders) {
	    crit.addOrder(c);
	}
	return (T)crit.uniqueResult();
    }
    

    /**
     * Use this inside subclasses as a convenience method.
     */
    @SuppressWarnings("unchecked")
    public List<T> findByCriteria(List<Order> orders, Criterion... criterion) {
	Criteria crit = getSession().createCriteria(getPersistentClass());
	crit.setMaxResults(ConfigReader.getInstance().getIntProperty(ConfigReader.MAX_ROW_RESULT));
	for (Criterion c : criterion) {
	    crit.add(c);
	}
	for (Order c : orders) {
	    crit.addOrder(c);
	}
	return crit.list();
    }

    /**
     * Utilizzare questo metodo per gestire criterion su collection/set, fornendo il Criteria(con settati alias per le collection) in input.
     */
    @SuppressWarnings("unchecked")
    public List<T> findByCriteria(Criteria crit, List<Order> orders, Criterion... criterion) {
	//se il criteria fornito in ingresso  null, lo creo
	if (crit == null) {
	    crit = getSession().createCriteria(getPersistentClass());
	}
	crit.setMaxResults(ConfigReader.getInstance().getIntProperty(ConfigReader.MAX_ROW_RESULT));
	for (Criterion c : criterion) {
	    crit.add(c);
	}
	for (Order c : orders) {
	    crit.addOrder(c);
	}
	return crit.list();
    }

    public void persist(T entity) {
	log.debug("persisting instance");
	try {
	    getSession().persist(entity);
	    log.debug("persist successful");
	} catch (RuntimeException re) {
	    log.error("persist failed", re);
	    throw re;
	}
    }

    public void attachDirty(T entity) {
	log.debug("attaching dirty instance");
	try {
	    getSession().saveOrUpdate(entity);
	    log.debug("attach successful");
	} catch (RuntimeException re) {
	    log.error("attach failed", re);
	    throw re;
	}
    }

    public void save(T entity) {
	log.debug("save dirty instance");
	try {
	    getSession().save(entity);
	    getSession().flush();
	    log.debug("save successful");
	} catch (RuntimeException re) {
	    log.error("save failed", re);
	    throw re;
	}
    }

    public void attachClean(T entity) {
	log.debug("attaching clean instance");
	try {
	    getSession().lock(entity, LockMode.NONE);
	    log.debug("attach successful");
	} catch (RuntimeException re) {
	    log.error("attach failed", re);
	    throw re;
	}
    }

    public void delete(T entity) {
	log.debug("deleting instance");
	try {
	    getSession().delete(entity);
	    log.debug("delete successful");
	} catch (RuntimeException re) {
	    log.error("delete failed", re);
	    throw re;
	}
    }
    
    @SuppressWarnings("unchecked")
	public void delete(ID id, boolean lock) {
	T entity;
	Session session;
	log.debug("deleting instance");
	try {
		session = getSession();
		if (lock)
			
		    entity = (T) session.get(getPersistentClass(), id, LockMode.UPGRADE);
		else
		    entity = (T) session.get(getPersistentClass(), id);
		
		session.delete(entity);
	    log.debug("delete successful");
	} catch (RuntimeException re) {
	    log.error("delete failed", re);
	    throw re;
	}
    }
    

    @SuppressWarnings("unchecked")
    public T merge(T entity) {
	log.debug("merging instance");
	try {
	    T result = (T) getSession().merge(entity);
	    log.debug("merge successful");
	    return result;
	} catch (RuntimeException re) {
	    log.error("merge failed", re);
	    throw re;
	}
    }

}